from Sodev.Block import Block
from Sodev import Instr
from Sodev.pcb import Pcb
from Sodev.blockHDD import BlockHDD

class Paginacion:
    
    def __init__(self, mem,hdd):
        self.freeBlocks = []
        self.usedBlocks = []
        self.memoriaFisica = mem
        self.setBlocks()
        self.hdd = hdd
        
        
    def setBlocks(self):
        index = 0
        for i in range(self.memoriaFisica.getSize() +1):
            if( index < self.memoriaFisica.getSize()):
                block = Block()
                block.setInitPos(index)
                block.setEndPos(index+1)
                self.freeBlocks.append(block)
                index = index + 2
            

        
    def write(self, progData,pid):
        if(self.hasSpace(self.analizeSizeOf(progData))):
            self.writeProgram(progData,pid)
        else:
            self.hdd.swapping(progData, pid)
        '''no hay?'''
            
            
    def hasSpace(self,size):
        ret = False
        n = 0
        for i in self.freeBlocks:
            n = n + i.size()
            if(i.size() >= size):
                ret = True
        
        return ret or (n>=size)
   
    def writeProgram(self, progData, pid):
        bloquesUsados = self.takeFreeBlocks(progData)
        intrsParaAlmacenar = self.aplanarInstrDeHDD(progData)
        for b in bloquesUsados:
            if(len(intrsParaAlmacenar) >= 2):
                self.memoriaFisica.write(b.getInitPos(), intrsParaAlmacenar.pop() )
                self.memoriaFisica.write(b.getEndPos(), intrsParaAlmacenar.pop() )
                b.setPid(pid)
            else:
                self.memoriaFisica.write(b.getInitPos(), intrsParaAlmacenar.pop() )
                self.memoriaFisica.write(b.getEndPos(), None )
                b.setPid(pid)
                
    
    #devuelve los bloques necesarios para guardar un programa y actualiza las listas
    # de bloqes
    def aplanarInstrDeHDD(self, progData):
        ret = []
        for bhdd in progData:
            for inst in bhdd.getInstrs():
                ret.append(inst)
        return ret
    
    def takeFreeBlocks(self,instrs):
        ret = []
        f = 0
        #guardo los bloques donde los voy a guardar las instruccions
        for b in self.freeBlocks:
            if(f < len(self.aplanarInstrDeHDD(instrs))):
                f = f + b.size()
                ret.append(b)
        #los elimino de la lista de libres
        for b in ret:
            self.freeBlocks.remove(b)
            self.usedBlocks.append(b)
        return ret
        
            
        
    '''Retorna "None" si se paso de index, o no encontro un bloque con el PID solicitado'''
    def read(self,pc,pid):
        intrs = self.getInstPid(pc, pid)
        if(len(intrs) > pc):
            return intrs[pc]
        else:
            return None
        
    def getInstPid(self, pc ,pid):
        bloq = []
        for b in self.usedBlocks:
            if(b.getPid()==pid):
                bloq.append(b)
        '''- copiar y borrar un programa en memoria >= al solicitado
        - copiar las instrucciones adentro de un bloquehdd
        - guardarlo en una lista como progdata
        - y hacer el swapping a disco
        '''
        if(len(bloq)==0):
            self.swapProgram(pid)
            self.write(self.hdd.getprogram(pid), pid)
            for b in self.usedBlocks:
                if(b.getPid()==pid):
                    bloq.append(b)
        return self.rotateNoneInList(self.getIntrsBlock(bloq))
    
    def rotateNoneInList(self,blocklist):
        tmpBlockL = blocklist
        if(tmpBlockL[0] == None):
            tmpBlockL.remove(None)
            tmpBlockL.append(None)
        return tmpBlockL
    
    def getIntrsBlock(self, bloques):
        ret = []
        for b in bloques:
            if(b.getEndPos() == None):
                ret.append(self.memoriaFisica.read(b.getInitPos()))
            else:
                ret.append(self.memoriaFisica.read(b.getInitPos()))
                ret.append(self.memoriaFisica.read(b.getEndPos()))
        return ret
    
    def swapProgram(self, pid):
        #pswap = self.hdd.getprogram(pid)
        #while (not self.hasSpace(self.analizeSizeOf(pswap))):
        if(len(self.usedBlocks)>0):
            bloq = self.usedBlocks[0]
            bloqUsed = []
            for b in self.usedBlocks:
                if(b.getPid() == bloq.getPid()):
                    bloqUsed.append(b)
                    
            instrs = self.getIntrsBlock(bloqUsed)
            pcb = Pcb(None,None,bloq.getPid(),None,None)
            self.liberarMemoria(pcb)
            bhdd = BlockHDD()
            bhdd.instrs = instrs
            progData = [bhdd]
            self.hdd.swapping(progData, bloq.getPid())
            
        
        
    
    def checkMarginsOfInstruction(self, block, pc):
        blocktmp = block
        ret = None
        if (pc + blocktmp.getInitPos() <= blocktmp.getEndPos()):
            ret = self.memoriaFisica.read(blocktmp.getInitPos() + pc)
        
        return ret
    
    def analizeSizeOf(self,progData):
        ret = 0
        for b in progData:
            ret = ret + b.size()
        return ret
    
    def getMemoriaFisica(self):
        return self.memoriaFisica
    
    
    def liberarMemoria(self, pcb):
        bUsed = []
        for b in self.usedBlocks:
            if(pcb.getPid() == b.getPid()):
                bUsed.append(b)
        for b in bUsed:
            if(not (b.getEndPos() == None)):
                self.memoriaFisica.write(b.getInitPos(), None)
                self.memoriaFisica.write(b.getEndPos(), None )
            else:
                self.memoriaFisica.write(b.getInitPos(),None )
            self.usedBlocks.remove(b)
            self.freeBlocks.append(b)
        
        
        
        
        